-
Notifications
You must be signed in to change notification settings - Fork 617
FIS getAuthToken implementation. #769
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
private static <F, T> Continuation<F, T> call(@NonNull Supplier<T> supplier) { | ||
return t -> { | ||
if (!t.isComplete()) { | ||
Tasks.await(t); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ciarand Instrumented tests fail sometimes because of this line. Its not strictly waiting for the prev Task (getId) to complete. Ends up calling refreshAuthTokenIfNecessary with empty PersistedFidEntry ( pointed you to that code).
Can you please help me to understand what am I missing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Test failure is alternating.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this is correct. You're calling Tasks.call(executor, this::getId)
but getId
is already calling Tasks.call
so you're doubly nesting your async operations.
Instead of this:
return Tasks.call(executor, this::getId)
.continueWith(call(() -> refreshAuthTokenIfNecessary(forceRefresh)));
I think you mean to do something like this:
return getId().continueWith((idTask) -> refreshAuthTokenIfNecessary(idTask.getResult(), forceRefresh));
NOTE: The call to getResult
will throw an Exception if the getId
task failed, which will cause the return value of this method (the getAuthToken
method) to be a failed Task, which is afaict what we want.
You're also not passing an explicit executor into the .continueWith call though, so the refreshAuthTokenIfNecessary method is getting run on the main thread (!). Given you're not allowed to call Tasks.await on the main thread, that seems like a bug.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very good catch. You are right. That was the issue :) Fixed it. About the 'Tasks.await' it wasn't a bug until I called Tasks.call(executor, this::getId)
. After the change recommended by you, I could not await in the Main Thread. I updated the code to await using the executor
. Let me know what you think.
...ase-installations/src/main/java/com/google/firebase/installations/FirebaseInstallations.java
Outdated
Show resolved
Hide resolved
...ndroidTest/java/com/google/firebase/installations/FirebaseInstallationsInstrumentedTest.java
Outdated
Show resolved
Hide resolved
...ase-installations/src/main/java/com/google/firebase/installations/FirebaseInstallations.java
Outdated
Show resolved
Hide resolved
...ase-installations/src/main/java/com/google/firebase/installations/FirebaseInstallations.java
Outdated
Show resolved
Hide resolved
...ase-installations/src/main/java/com/google/firebase/installations/FirebaseInstallations.java
Outdated
Show resolved
Hide resolved
private static <F, T> Continuation<F, T> call(@NonNull Supplier<T> supplier) { | ||
return t -> { | ||
if (!t.isComplete()) { | ||
Tasks.await(t); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this is correct. You're calling Tasks.call(executor, this::getId)
but getId
is already calling Tasks.call
so you're doubly nesting your async operations.
Instead of this:
return Tasks.call(executor, this::getId)
.continueWith(call(() -> refreshAuthTokenIfNecessary(forceRefresh)));
I think you mean to do something like this:
return getId().continueWith((idTask) -> refreshAuthTokenIfNecessary(idTask.getResult(), forceRefresh));
NOTE: The call to getResult
will throw an Exception if the getId
task failed, which will cause the return value of this method (the getAuthToken
method) to be a failed Task, which is afaict what we want.
You're also not passing an explicit executor into the .continueWith call though, so the refreshAuthTokenIfNecessary method is getting run on the main thread (!). Given you're not allowed to call Tasks.await on the main thread, that seems like a bug.
2. Renaming InstallationTokenResult TokenExpirationTimestampMillis field to TokenExpirationInSecs
...ations-interop/src/main/java/com/google/firebase/installations/FirebaseInstallationsApi.java
Outdated
Show resolved
Hide resolved
@@ -217,4 +225,22 @@ public void testGetId_PersistedFidError_BackendOk() throws InterruptedException | |||
.isEqualTo(FirebaseInstallationsException.Status.CLIENT_ERROR); | |||
} | |||
} | |||
|
|||
@Test | |||
public void testGetAuthToken_registeredFid_successful() throws Exception { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just added one test to review the IntDef usage. I will add more tests, once that is reviewed . Thank you.
2. Renaming InstallationTokenResult TokenExpirationTimestampMillis field to TokenExpirationInSecs
...ations-interop/src/main/java/com/google/firebase/installations/FirebaseInstallationsApi.java
Outdated
Show resolved
Hide resolved
...ndroidTest/java/com/google/firebase/installations/FirebaseInstallationsInstrumentedTest.java
Outdated
Show resolved
Hide resolved
...ase-installations/src/main/java/com/google/firebase/installations/FirebaseInstallations.java
Outdated
Show resolved
Hide resolved
@@ -169,6 +180,15 @@ private static boolean persistedFidMissingOrInErrorState(PersistedFidEntry persi | |||
}; | |||
} | |||
|
|||
@NonNull | |||
private <F, T> Continuation<F, T> call(@NonNull Supplier<T> supplier) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
give this method a more specific name?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. PTAL.
...ase-installations/src/main/java/com/google/firebase/installations/FirebaseInstallations.java
Show resolved
Hide resolved
...ations-interop/src/main/java/com/google/firebase/installations/FirebaseInstallationsApi.java
Show resolved
Hide resolved
Friendly ping !! |
2. Minor code changes as per the offline discussion with Rayo
run ./gradlew :firebase-installations:generateApiTxtFile and commit the changes to fix the api-information bug |
same thing with :firebase-installations-interop:apiInformation project as well |
/test new-smoke-tests |
|
||
@Before | ||
public void setUp() throws FirebaseInstallationServiceException { | ||
MockitoAnnotations.initMocks(this); | ||
FirebaseApp.clearInstancesForTest(); | ||
executor = new ThreadPoolExecutor(0, 2, 10L, TimeUnit.SECONDS, new SynchronousQueue<>()); | ||
executor = new ThreadPoolExecutor(0, 4, 10L, TimeUnit.SECONDS, new SynchronousQueue<>()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you add a comment explaining why you've chosen 4 for this test?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. PTAL.
...ndroidTest/java/com/google/firebase/installations/FirebaseInstallationsInstrumentedTest.java
Outdated
Show resolved
Hide resolved
Throwable cause = expected.getCause(); | ||
assertWithMessage("Exception class doesn't match") | ||
.that(cause) | ||
.isInstanceOf(FirebaseInstallationsException.class); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Truth has built-in support for unpacking exceptions
assertWithMessage("w/e").that(expected).hasCauseThat().isInstanceOf(FirebaseInstallationsException.class)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. Did you also want the status to be changed? I tried but dint find a easy way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You'd have to create a custom truth subject for that and tbh I don't think it's worth it atm
|
||
@Test | ||
public void testGetAuthToken_unregisteredFid_fetchedNewTokenFromFIS() throws Exception { | ||
when(mockPersistedFid.readPersistedFidEntryValue()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
imo this test could do with some comments explaining what you're testing
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. PTAL.
...ndroidTest/java/com/google/firebase/installations/FirebaseInstallationsInstrumentedTest.java
Outdated
Show resolved
Hide resolved
firebaseInstallations.getAuthToken(FirebaseInstallationsApi.FORCE_REFRESH); | ||
Thread.sleep(500); | ||
Task<InstallationTokenResult> task2 = | ||
firebaseInstallations.getAuthToken(FirebaseInstallationsApi.FORCE_REFRESH); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we shouldn't have to sleep in tests. Can we somehow use the injected to ensure these tasks are run?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wanted the sleep to ensure task executed in order. But I removed it, instead of validating on both expected values
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why don't they execute in order at the moment? Is it because the executor has 4 threads? Could we use a single threaded executor to make this deterministic?
|
||
class ServiceClient extends FirebaseInstallationServiceClient { | ||
|
||
private boolean secondCall; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this should be explained or reworked imo, it's not super obvious what this helper and its test are checking.
Could you use an inline mockito answer (thenAnswer instead of thenThrow / thenReturn) to clarify the goal of the test?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the suggestion. I have tried using thenAnswer. PTAL.
firebase-installations/src/main/java/com/google/firebase/installations/AwaitListener.java
Outdated
Show resolved
Hide resolved
final class AwaitListener implements OnCompleteListener<Void> { | ||
private final CountDownLatch mLatch = new CountDownLatch(1); | ||
|
||
public void onSuccess() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this isn't actually used, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Its used. If the status is REGISTERED, instead of waiting for 10s, I am using this method to update the awaitlistener.
FirebaseInstalllations.java line 261
@NonNull Supplier<T> supplier, AwaitListener listener) { | ||
return t -> { | ||
// Waiting for Task that registers FID on the FIS Servers | ||
listener.await(10, TimeUnit.SECONDS); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
move this to a constant imo
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
/test check-changed |
...lations-interop/src/main/java/com/google/firebase/installations/InstallationTokenResult.java
Outdated
Show resolved
Hide resolved
...ndroidTest/java/com/google/firebase/installations/FirebaseInstallationsInstrumentedTest.java
Outdated
Show resolved
Hide resolved
Throwable cause = expected.getCause(); | ||
assertWithMessage("Exception class doesn't match") | ||
.that(cause) | ||
.isInstanceOf(FirebaseInstallationsException.class); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You'd have to create a custom truth subject for that and tbh I don't think it's worth it atm
...ndroidTest/java/com/google/firebase/installations/FirebaseInstallationsInstrumentedTest.java
Outdated
Show resolved
Hide resolved
...ndroidTest/java/com/google/firebase/installations/FirebaseInstallationsInstrumentedTest.java
Outdated
Show resolved
Hide resolved
...ndroidTest/java/com/google/firebase/installations/FirebaseInstallationsInstrumentedTest.java
Outdated
Show resolved
Hide resolved
// and verify one task waits for another task to complete. | ||
|
||
doAnswer( | ||
new AnswersWithDelay( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not 100% sure here, but I think we're supposed to use the AdditionalAnswers class to create these instead of reaching into the mockito internal package: https://static.javadoc.io/org.mockito/mockito-core/2.9.0/org/mockito/AdditionalAnswers.html
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the link. I dint realize it was internal package. Updated.
firebaseInstallations.getAuthToken(FirebaseInstallationsApi.FORCE_REFRESH); | ||
Thread.sleep(500); | ||
Task<InstallationTokenResult> task2 = | ||
firebaseInstallations.getAuthToken(FirebaseInstallationsApi.FORCE_REFRESH); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why don't they execute in order at the moment? Is it because the executor has 4 threads? Could we use a single threaded executor to make this deterministic?
...ndroidTest/java/com/google/firebase/installations/FirebaseInstallationsInstrumentedTest.java
Outdated
Show resolved
Hide resolved
@@ -480,10 +476,10 @@ public void testGetAuthToken_multipleCallsForceRefresh_fetchedNewTokenTwice() th | |||
// As we cannot ensure which task got executed first, verifying with both expected values | |||
assertWithMessage("Persisted Auth Token doesn't match") | |||
.that(task1.getResult().getToken()) | |||
.isAnyOf(TEST_AUTH_TOKEN_3, TEST_AUTH_TOKEN_4); | |||
.isEqualTo(TEST_AUTH_TOKEN_3); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nice
No description provided.